﻿/*
Digraph (library for building, traversal, comparison and saving of directed
graph).

Copyright (C) 2014 Dmitry Merzagitovich Arslanov

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;

namespace Digraph
{
    /// <summary>
    /// Provides digraph creation. If builder is not available for digraph,
    /// user can not change such digraph.
    /// </summary>
    /// <typeparam name="TVertex">Type of digraph vertex content.</typeparam>
    /// <typeparam name="TArc">Type of digraph arc content.</typeparam>
    public sealed class Builder<TVertex, TArc>
    {
        /// <summary>
        /// Created digraph.
        /// </summary>
        public Set<TVertex, TArc> Digraph { get; private set; }

        /// <summary>
        /// Create new instance of digraph builder.
        /// </summary>
        public Builder()
        {
            NewDigraph();
        }

        /// <summary>
        /// Replace Digraph with new instance for creating.
        /// </summary>
        public void NewDigraph()
        {
            Digraph = new Set<TVertex, TArc>();
        }

        /// <summary>
        /// Create new digraph vertex.
        /// </summary>
        /// <param name="content">Content of vertex.</param>
        /// <returns>Created digraph vertex.</returns>
        /// <exception cref="VertexAlreadyExistsException">
        /// This exception is thrown if user tries to add vertex in digraph that
        /// already exists in this digraph.
        /// </exception>
        public Vertex<TVertex, TArc> NewVertex(TVertex content)
        {
            Vertex<TVertex, TArc> vertex = new Vertex<TVertex, TArc>(content);
            if (Digraph.VertexList.Contains(vertex))
                throw new VertexAlreadyExistsException();
            Digraph.VertexList.Add(vertex);
            return vertex;
        }

        /// <summary>
        /// Delete existing digraph vertex.
        /// </summary>
        /// <param name="vertex">Vertex for deleting.</param>
        /// <exception cref="VertexDoesNotExistException">
        /// This exception is thrown if user tries to delete vertex from
        /// digraph that does not exist in this digraph.
        /// </exception>
        public void DeleteVertex(Vertex<TVertex, TArc> vertex)
        {
            if (!Digraph.VertexList.Contains(vertex))
                throw new VertexDoesNotExistException();
            Digraph.VertexList.Remove(vertex);
            foreach (Arc<TVertex, TArc> incomingArc in vertex.IncomingArcList)
            {
                incomingArc.FromVertex.OutcomingArcList.Remove(incomingArc);
                Digraph.ArcList.Remove(incomingArc);
                incomingArc.FromVertex = null;
                incomingArc.ToVertex = null;
            }
            foreach (Arc<TVertex, TArc> outcomingArc in vertex.OutcomingArcs)
            {
                outcomingArc.ToVertex.IncomingArcList.Remove(outcomingArc);
                Digraph.ArcList.Remove(outcomingArc);
                outcomingArc.FromVertex = null;
                outcomingArc.ToVertex = null;
            }
            vertex.IncomingArcList.Clear();
            vertex.OutcomingArcList.Clear();
        }

        /// <summary>
        /// Create new digraph arc.
        /// </summary>
        /// <param name="fromVertex">
        /// Vertex from which the arc outcomes.
        /// </param>
        /// <param name="toVertex">Vertex to which the arc incomes.</param>
        /// <param name="content">Content of arc.</param>
        /// <returns>Created digraph arc.</returns>
        /// <exception cref="VertexDoesNotExistException">
        /// This exception is thrown if user tries to use vertex from
        /// digraph that does not exist in this digraph.
        /// </exception>
        /// <exception cref="ArcAlreadyExistsException">
        /// This exception is thrown if user tries to add arc in digraph that
        /// already exists in this digraph.
        /// </exception>
        public Arc<TVertex, TArc> NewArc(Vertex<TVertex, TArc> fromVertex,
                                         Vertex<TVertex, TArc> toVertex,
                                         TArc content = default(TArc))
        {
            if (!Digraph.VertexList.Contains(fromVertex))
                throw new VertexDoesNotExistException();
            if (!Digraph.VertexList.Contains(toVertex))
                throw new VertexDoesNotExistException();
            Arc<TVertex, TArc> arc = new Arc<TVertex, TArc>(fromVertex,
                                                            toVertex,
                                                            content);
            if (Digraph.ArcList.Contains(arc))
                throw new ArcAlreadyExistsException();
            Digraph.ArcList.Add(arc);
            fromVertex.OutcomingArcList.Add(arc);
            toVertex.IncomingArcList.Add(arc);
            return arc;
        }

        /// <summary>
        /// Delete existing digraph arc.
        /// </summary>
        /// <param name="arc">Arc for deleting.</param>
        /// <exception cref="ArcDoesNotExistException">
        /// This exception is thrown if user tries to delete arc in digraph that does not
        /// exist in created digraph.
        /// </exception>
        public void DeleteArc(Arc<TVertex, TArc> arc)
        {
            if (!Digraph.ArcList.Contains(arc))
                throw new ArcDoesNotExistException();
            Digraph.ArcList.Remove(arc);
            arc.FromVertex.OutcomingArcList.Remove(arc);
            arc.ToVertex.IncomingArcList.Remove(arc);
            arc.FromVertex = null;
            arc.ToVertex = null;
        }

        /// <summary>
        /// Load digraph from tgf string:
        /// <para>1 Content of first vertex</para>
        /// <para>2 Content of second vertex</para>
        /// <para>#</para>
        /// <para>1 2 Content of arc between first and second vertices</para>
        /// Provide <see cref="System.ComponentModel.TypeConverter" /> to load
        /// vertex and arc contents from strings.
        /// </summary>
        /// <param name="tgfString">
        /// String representation of digraph in tgf format.
        /// </param>
        /// <exception cref="CantConvertStringToVertexException">
        /// This exception is thrown if
        /// <see cref="System.ComponentModel.TypeConverter" /> is not specified
        /// for vertex content type which provides string to vertex conversion.
        /// </exception>
        /// <exception cref="CantConvertStringToArcException">
        /// This exception is thrown if
        /// <see cref="System.ComponentModel.TypeConverter" /> is not specified
        /// for arc content type which provides string to arc conversion.
        /// </exception>
        public static Builder<TVertex, TArc> Parse(string tgfString)
        {
            Type stringType = typeof(string);
            TypeConverter vtxConv = TypeDescriptor.GetConverter(
                typeof(TVertex));
            if (!vtxConv.CanConvertFrom(stringType))
                throw new CantConvertStringToVertexException();
            TypeConverter arcConv = TypeDescriptor.GetConverter(
                typeof(TArc));
            if (!arcConv.CanConvertFrom(stringType))
                throw new CantConvertStringToArcException();
            StringReader reader = new StringReader(tgfString);
            Dictionary<string, Vertex<TVertex, TArc>> vertices =
                new Dictionary<string, Vertex<TVertex, TArc>>();
            Builder<TVertex, TArc> builder = new Builder<TVertex, TArc>();
            bool readVertices = true;
            while (true)
            {
                string line = reader.ReadLine();
                if (string.IsNullOrEmpty(line)) break;
                line = line.Trim();
                if (string.IsNullOrEmpty(line)) continue;
                if (line == "#")
                {
                    readVertices = false;
                    continue;
                }
                if (readVertices)
                {
                    int i = line.IndexOf(' ');
                    if (i < 0)
                    {
                        Vertex<TVertex, TArc> vertex = builder.NewVertex(
                            default(TVertex));
                        vertices.Add(line, vertex);
                    }
                    else
                    {
                        string id = line.Substring(0, i);
                        string cStr = line.Substring(i + 1);
                        TVertex cnt = (TVertex)vtxConv.ConvertFromString(cStr);
                        Vertex<TVertex, TArc> vertex = builder.NewVertex(cnt);
                        vertices.Add(id, vertex);
                    }
                }
                else
                {
                    int i = line.IndexOf(' ');
                    if (i > 0)
                    {
                        int ni = i + 1;
                        int j = line.IndexOf(' ', ni);
                        if (j > 0)
                        {
                            int nj = j + 1;
                            string id1 = line.Substring(0, i);
                            string id2 = line.Substring(ni, j - ni);
                            string cStr = line.Substring(nj);
                            TArc cnt = (TArc)arcConv.ConvertFromString(cStr);
                            Vertex<TVertex, TArc> v1 = vertices[id1];
                            Vertex<TVertex, TArc> v2 = vertices[id2];
                            builder.NewArc(v1, v2, cnt);
                        }
                        else
                        {
                            string id1 = line.Substring(0, i);
                            string id2 = line.Substring(ni);
                            Vertex<TVertex, TArc> v1 = vertices[id1];
                            Vertex<TVertex, TArc> v2 = vertices[id2];
                            builder.NewArc(v1, v2);
                        }
                    }
                }
            }
            return builder;
        }

        /// <summary>
        /// Load digraph from tgf stream with specified encoding:
        /// <para>1 Content of first vertex</para>
        /// <para>2 Content of second vertex</para>
        /// <para>#</para>
        /// <para>1 2 Content of arc between first and second vertices</para>
        /// Provide <see cref="System.ComponentModel.TypeConverter" /> to load
        /// vertex and arc contents from strings.
        /// </summary>
        /// <param name="stream">
        /// Stream from where digraph in tgf is loaded.
        /// </param>
        /// <param name="encoding">Stream encoding.</param>
        /// <returns>Loaded digraph builder.</returns>
        public static Builder<TVertex, TArc> Load(Stream stream,
                                                  Encoding encoding)
        {
            StreamReader reader = new StreamReader(stream, encoding);
            string tgfString = reader.ReadToEnd();
            return Parse(tgfString);
        }

        /// <summary>
        /// Load digraph from tgf stream with default encoding:
        /// <para>1 Content of first vertex</para>
        /// <para>2 Content of second vertex</para>
        /// <para>#</para>
        /// <para>1 2 Content of arc between first and second vertices</para>
        /// Provide <see cref="System.ComponentModel.TypeConverter" /> to load
        /// vertex and arc contents from strings.
        /// </summary>
        /// <param name="stream">
        /// Stream from where digraph in tgf is loaded.
        /// </param>
        public static Builder<TVertex, TArc> Load(Stream stream)
        {
            return Load(stream, Encoding.Default);
        }

        /// <summary>
        /// Load digraph from tgf file with specified encoding:
        /// <para>1 Content of first vertex</para>
        /// <para>2 Content of second vertex</para>
        /// <para>#</para>
        /// <para>1 2 Content of arc between first and second vertices</para>
        /// Provide <see cref="System.ComponentModel.TypeConverter" /> to load
        /// vertex and arc contents from strings.
        /// </summary>
        /// <param name="fileName">Tgf file name.</param>
        /// <param name="encoding">File encoding.</param>
        public static Builder<TVertex, TArc> Load(string fileName,
                                                  Encoding encoding)
        {
            using (FileStream file = File.OpenRead(fileName))
            {
                return Load(file, encoding);
            }
        }

        /// <summary>
        /// Load digraph from tgf file with default encoding:
        /// <para>1 Content of first vertex</para>
        /// <para>2 Content of second vertex</para>
        /// <para>#</para>
        /// <para>1 2 Content of arc between first and second vertices</para>
        /// Provide <see cref="System.ComponentModel.TypeConverter" /> to load
        /// vertex and arc contents from strings.
        /// </summary>
        /// <param name="fileName">Tgf file name.</param>
        public static Builder<TVertex, TArc> Load(string fileName)
        {
            return Load(fileName, Encoding.Default);
        }
    }
}
